/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package cafe.Model.Utility;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Deka
 */
public class DBConnectionPool {

    private static DBConnectionPool ConnectionPoolInstance = null; //singleton

    //==========================================================================
    //pool body
    private BlockingQueue<DBConnection> connectionQueue = null;

    //count existed connections in queue
    private AtomicInteger connectionCount = new AtomicInteger();

    //min connection (innitialize)
    private int innitializeConnection = 10;

    //max connection
    private int maxConnection = 100;

    //database info (for all Connection in pool
    private DatabaseInfo databaseInfo;

    private DBConnectionPool() {
        this.loadConfig();

        this.connectionQueue = new LinkedBlockingQueue<>();

        for (int i = 0; i < innitializeConnection; i++) {
            DBConnection connectObject = new DBConnection(databaseInfo);

            //add connection to queue
            this.add(connectObject);
        }
    }

    /**
     * load config from file "config/database-connection.properties"
     */
    public void loadConfig() {
        Properties prop = new Properties();

        String fileName = "config/database-connection.properties";
        try {
            InputStream is = new FileInputStream(fileName);
            prop.load(is);
            is.close();
        } catch (FileNotFoundException ex) {
            Logger.getLogger(DBConnectionPool.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(DBConnectionPool.class.getName()).log(Level.SEVERE, null, ex);
        }

        String driver = prop.getProperty("db.driver");
        String dbms = prop.getProperty("db.dbms");
        String server = prop.getProperty("db.server");
        String port = prop.getProperty("db.port");
        String database = prop.getProperty("db.databasename");
        String sUsername = prop.getProperty("db.username");
        String sPassword = prop.getProperty("db.password");

        this.databaseInfo = new DatabaseInfo(driver, dbms, server, port,
                database, sUsername, sPassword);
        this.innitializeConnection = Integer.parseInt(prop.getProperty("pool.min"));
        this.maxConnection = Integer.parseInt(prop.getProperty("pool.max"));
    }

    public static DBConnectionPool getInstance() {
        if (ConnectionPoolInstance == null) {
            ConnectionPoolInstance = new DBConnectionPool();
        }

        return ConnectionPoolInstance;
    }

    private void add(DBConnection connectObject) {
        try {
            //Inserts the specified element into this queue
            //waiting up to 100 miliseconds if necessary for space to become available
            connectionQueue.offer(connectObject, 100, TimeUnit.MILLISECONDS);

            connectionCount.incrementAndGet();
        } catch (InterruptedException ex) {

        }
    }

    public DBConnection getConnection() throws Exception {
        DBConnection conn = null;
        try
        {
            //just wait for 5 miliseconds to find an available connection 
            conn = connectionQueue.poll(5, TimeUnit.MILLISECONDS);
            
            
            
            //if can't find an availabe connection 
            //try to create a new connection to use
            if(conn == null)
            {
                //just create one by one connection
                //to check the maxConnection
                synchronized (connectionCount)
                {
                    if(connectionCount.get() < maxConnection)
                    {
                        conn = new DBConnection(this.databaseInfo);
                        
                        connectionQueue.offer(conn,100, TimeUnit.MILLISECONDS);
                        
                        connectionCount.incrementAndGet();
                    }
                    
                    if(conn == null)
                    {
                        throw new Exception("BlockingQueueConnectionPool - getConnectionObject() : CONNECTION IS STILL NULL.");
                    }
                    else
                    {
                        return connectionQueue.poll();
                    }
                }
            }
        }
        catch (InterruptedException ex)
        {
            Logger.getLogger(DBConnectionPool.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        return conn;
    }

    /**
     * give Connection back to Pool
     * @param connectionObject 
     */
    public void giveBack(DBConnection connectionObject) {
        if(connectionObject != null)
        {
            try
            {
                connectionQueue.offer(connectionObject, 100, TimeUnit.MILLISECONDS);
                
            }
            catch (InterruptedException ex)
            {
                Logger.getLogger(DBConnectionPool.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}
